//
//  USBSensor.m
//  Pennyworth
//
//  Created by Chris Karr on 12/24/07.
//  Copyright 2007 Chris J. Karr. All rights reserved.
//

#import "USBSensor.h"

#import <IOKit/usb/IOUSBLib.h>
#import <IOKit/IOCFPlugIn.h>

#import "Context.h"
#import "SensorsController.h"

#define SENSOR_NAME @"USB Devices"

static void devicesChanged (void *ref, io_iterator_t iterator)
{
    while (IOIteratorNext (iterator))
    {

	}
	
	USBSensor * self = (USBSensor *) ref;
	[self update];
}

@implementation USBSensor

- (void) update
{
	if (![[SensorsController sharedController] canFire:SENSOR_NAME])
	{
		NSMutableDictionary * note = [NSMutableDictionary dictionary];
		[note setValue:SENSOR_NAME forKey:OBSERVATION_SENSOR];
		[[NSNotificationCenter defaultCenter] postNotificationName:OBSERVATION_UPDATE object:self userInfo:note];
		
		return;
	}		
	
	mach_port_t masterPort;
    CFMutableDictionaryRef matchingDict;

    kern_return_t kr;
 
    //Create a master port for communication with the I/O Kit
    kr = IOMasterPort (MACH_PORT_NULL, &masterPort);
    if (kr || !masterPort)
    {
        NSLog (@"ERR: Couldn't create a master I/O Kit port(%08x)", kr);
        return;
    }

    //Set up matching dictionary for class IOUSBDevice and its subclasses
    matchingDict = IOServiceMatching (kIOUSBDeviceClassName);
    if (!matchingDict)
    {
        NSLog (@"Couldn't create a USB matching dictionary");
        mach_port_deallocate(mach_task_self(), masterPort);
        return;
    }
 
	io_iterator_t iter;
	IOServiceGetMatchingServices (kIOMasterPortDefault, matchingDict, &iter);

	NSMutableArray * foundDevices = [NSMutableArray array];
	
	io_service_t usbDevice;
    while (usbDevice = IOIteratorNext (iter))
    {
		IOCFPlugInInterface	**plugInInterface = NULL;
		SInt32 score;

		//Create an intermediate plug-in
        kr = IOCreatePlugInInterfaceForService(usbDevice, kIOUSBDeviceUserClientTypeID, kIOCFPlugInInterfaceID, &plugInInterface, &score);

        if ((kIOReturnSuccess != kr) || !plugInInterface)
            printf("Unable to create a plug-in (%08x)\n", kr);

		IOUSBDeviceInterface182 **dev = NULL;
    
        //Now create the device interface
		HRESULT result = (*plugInInterface)->QueryInterface(plugInInterface, CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID), (LPVOID)&dev);

        if (result || !dev)
            printf("Couldn't create a device interface (%08x)\n", (int) result);

		UInt16 vendor;
		UInt16 product;
		UInt16 release;
 
        //Check these values for confirmation
        (*dev)->GetDeviceVendor(dev, &vendor);
        (*dev)->GetDeviceProduct(dev, &product);
        (*dev)->GetDeviceReleaseNumber(dev, &release);

        (*dev)->GetDeviceReleaseNumber(dev, &release);

		UInt8 stringIndex;
        
		(*dev)->USBGetProductStringIndex(dev, &stringIndex);

		IOUSBConfigurationDescriptorPtr desc;

        (*dev)->GetConfigurationDescriptorPtr(dev, stringIndex, &desc);

		io_name_t deviceName;
		kr = IORegistryEntryGetName (usbDevice, deviceName);
		if (kr != KERN_SUCCESS)
		{
			NSLog (@"fail 0x%8x", kr);
			deviceName[0] = '\0';
		}
		
		NSString * name = [NSString stringWithCString:deviceName encoding:NSASCIIStringEncoding];

		[foundDevices addObject:[NSString stringWithFormat:@"%@ (0x%x 0x%x 0x%x)", name, vendor, product, release]];

        IOObjectRelease(usbDevice);
        (*plugInInterface)->Release(plugInInterface);
        (*dev)->Release(dev);
    }
 
    //Finished with master port
    mach_port_deallocate(mach_task_self(), masterPort);
    masterPort = 0;

	[foundDevices sortUsingSelector:@selector(compare:)];

	NSMutableDictionary * note = [NSMutableDictionary dictionary];
	
	[note setValue:@"USB Devices" forKey:OBSERVATION_SENSOR];
	[note setValue:foundDevices forKey:OBSERVATION_OBSERVATION];

	[[NSNotificationCenter defaultCenter] postNotificationName:OBSERVATION_UPDATE object:self userInfo:note];
}

- (void) awakeFromNib
{
	[[SensorsController sharedController] addSensor:SENSOR_NAME];
	
	port = IONotificationPortCreate (kIOMasterPortDefault);
	loop = IONotificationPortGetRunLoopSource (port);
	CFRunLoopAddSource (CFRunLoopGetCurrent (), loop, kCFRunLoopDefaultMode);
	
	CFDictionaryRef matchDict = IOServiceMatching (kIOUSBDeviceClassName);
	matchDict = CFRetain (matchDict);

	IOServiceAddMatchingNotification (port, kIOFirstMatchNotification, matchDict, devicesChanged, (void *) self, &iterator);
	devicesChanged ((void *) self, iterator);

	IOServiceAddMatchingNotification (port, kIOTerminatedNotification, matchDict, devicesChanged, (void *) self, &iterator);
	devicesChanged ((void *) self, iterator);

	[[NSTimer scheduledTimerWithTimeInterval:10 target:self selector:@selector(update:) userInfo:nil repeats:NO] retain];
	
	[self update];
}

- (void) update:(NSTimer *) theTimer
{
	[theTimer release];
	
	[self update];
}

- (void) free
{
	CFRunLoopRemoveSource (CFRunLoopGetCurrent(), loop, kCFRunLoopDefaultMode);
	IONotificationPortDestroy (port);
	IOObjectRelease (iterator);
}

@end
